package l1j.server.echo;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.math.BigInteger;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.logging.Level;
import java.util.logging.Logger;

import l1j.opqlo.List.OnlineUser;
import l1j.opqlo.Server.QuitGame;
import l1j.server.Config;
import l1j.server.GameServer.GameServer;
import l1j.server.GameServer.LanSecurityManager;
import l1j.server.Thread.GeneralThreadPool;
import l1j.server.Thread.PcOtherThreadPool;
import l1j.server.encryptions.Encryption;
import l1j.server.server.datatables.lock.AccountReading;
import l1j.server.server.model.L1Location;
import l1j.server.server.model.Instance.L1PcInstance;
import l1j.server.server.serverpackets.S_Disconnect;
import l1j.server.server.templates.L1Account;
import l1j.server.server.utils.StreamUtil;

// Referenced classes of package l1j.server.server:
// PacketHandler, Logins, IpTable, LoginController,
// ClanTable, IdFactory
//
public class ClientThread extends OpcodesClient implements Runnable {

	/**
	 * for Test
	 */
	/*
	 * protected ClientThread() { }
	 */

	public ClientThread(final Socket socket) throws IOException {
		this._csocket = socket;
		final int randomNumber = (int) (Math.random() * 900000000) + 255;
		this._xorByte = randomNumber % 255 + 1;
		this._authdata = new BigInteger(Integer.toString(randomNumber)).modPow(
				new BigInteger(Config.RSA_KEY_E),
				new BigInteger(Config.RSA_KEY_N)).longValue();

		this.isError = true;
		//		this._ip = new StringBuilder().append(socket.getInetAddress().getHostAddress());
		this._handler = new PacketHandler(this);
		this._keys = new Encryption();
		this._decrypt = new DecryptExecutor(this, socket.getInputStream());
		this._encrypt = new EncryptExecutor(this, socket.getOutputStream());
		//		this._mac = new StringBuilder().append(socket.getInetAddress().getHostName());

		// PacketHandler 初期設定
	}

	@Override
	public void run() {
		final HcPacket movePacket = new HcPacket(M_CAPACITY);
		GeneralThreadPool.getInstance().schedule(movePacket, 0);

		final PacketHc hcPacket = new PacketHc(this, H_CAPACITY);
		GeneralThreadPool.getInstance().schedule(hcPacket, 0);

		//		final PacketHc a = new PacketHc(this, A);
		//		GeneralThreadPool.getInstance().schedule(a, 0);

		//		final PacketHc b = new PacketHc(this, B);
		//		GeneralThreadPool.getInstance().schedule(b, 0);

		//		final PacketHc s = new PacketHc(this, S);
		//		GeneralThreadPool.getInstance().schedule(s, 0);

		//		final PacketHc i = new PacketHc(this, I);
		//		GeneralThreadPool.getInstance().schedule(i, 0);

		// 加入人物自動保存時間軸
		this.set_savePc(Config.AUTOSAVE_INTERVAL);
		// 加入背包物品自動保存時間軸
		this.set_saveInventory(Config.AUTOSAVE_INTERVAL_INVENTORY);
		//		getUUID();
		//		System.out.println(_uuid);

		setIp();
		try {
			if(Config.IS_ON_ATTACK){
				LanSecurityManager.BANIPPACK.put(_ip.toString(), 300);
			}
			this._encrypt.satrt();// 開始處理封包輸出
			this._encrypt.outStart();// 送出第一組封包
			boolean isEcho = false;// 完成要求接收伺服器版本(防止惡意封包發送)

			while (this._isOnline) {
				byte[] data = null;
				try {
					data = this.readPacket();
				} catch (final Exception e) {
					_log.log(Level.SEVERE, e.getLocalizedMessage(), e);
					break;
				}
				if (data == null) {
					break;
				}
				if (data.length > 1440) {
					_log.info("客戶端送出長度異常封包:"
							+ this._ip.toString()
							+ " 帳號:"
							+ (this._account != null ? this._account
									.get_login() : "未登入"));
					if(Config.IS_ON_ATTACK){
						LanSecurityManager.BANIPPACK.put(this._ip.toString(), 300);
					}
					break;
				}
				if (this._account != null) {
					if (!OnlineUser.get().isLan(this._account.get_login())) {
						break;
					}
					if (!this._account.is_isLoad()) {
						break;
					}
				}

				// 要處理的 OPCODE
				final int opcode = data[0] & 0xFF;
				//								 System.out.println("[Client] opcode = " + opcode);//opqlo
				// OPCODE檢查

				// nullの場合はキャラクター選択前なのでOpcodeの取捨選択はせず全て実行
				if (this._activeChar == null) {
					if (opcode == C_OPCODE_CLIENTVERSION) {// 要求接收伺服器版本
						if (this._handler == null) {
							_log.info("請求接收封包異常:"
									+ this._ip.toString()
									+ " 帳號:"
									+ (this._account != null ? this._account
											.get_login() : "未登入"));
							break;
						}
						if(Config.IS_ON_ATTACK){
							LanSecurityManager.BANIPPACK.remove(this._ip.toString());
						}
						isEcho = true;
						isError = false;
						if (this.stringBuilder == null) {
							this.stringBuilder = new StringBuilder();
						}
						this.stringBuilder
						.append("\n--------------------------------------------------\n       客戶端 連線: (");
						this.stringBuilder.append(this._mac + " / " + this._ip);
						this.stringBuilder
						.append(") 完成連線建立!!\n--------------------------------------------------");
						_log.info(this.stringBuilder.toString());
						this.stringBuilder.delete(0,this.stringBuilder.length());

						// 自動踢人
						final ClientThreadObserver cick = new ClientThreadObserver();
						PcOtherThreadPool.get().execute(cick);
					}
					if (isEcho) {
						this._handler.handlePacket(data);
					}
					continue;
				}
				if (!isEcho) {
					continue;
				}

				// 切換角色、丟道具到地上、刪除身上道具
				switch (opcode) {
				case C_OPCODE_QUITGAME:// 要求離開遊戲
					this._handler.handlePacket(data);
					break;
				case C_OPCODE_CHANGECHAR: // 要求切換角色
					this._handler.handlePacket(data);
					break;
				case C_OPCODE_DROPITEM: // 要求丟棄物品(丟棄置地面)
					this._handler.handlePacket(data);
					break;
				case C_OPCODE_DELETEINVENTORYITEM: // 要求刪除物品
					this._handler.handlePacket(data);
					break;
				case C_OPCODE_MOVECHAR: // 人物移動封包處理
					// // 為了確保即時的移動，將移動的封包獨立出來處理
					movePacket.requestWork(data);
					break;

				default: // 其他封包處理
					hcPacket.requestWork(data);
					this.packetReceived();// opqlo 空線程判定
					break;
				}
			}
		} catch (final Exception e) {
			_log.log(Level.SEVERE, e.getLocalizedMessage(), e);
		} finally {
			//			IpAttackCheck.SOCKETLIST.remove(this);

			// 移出人物自動保存時間軸
			this.set_savePc(-1);
			// 移出背包物品自動保存時間軸
			this.set_saveInventory(-1);

			// 關閉IO
			this.close();
		}
		// _log.fine("Server thread[C] stopped");
		return;
	}

	/** 自動踢除沒有客戶端的線程 */
	class ClientThreadObserver implements Runnable {

		private int _checkCount;
		private int _closeCount;
		private final String _logip;

		/** 自動踢除沒有客戶端的線程 */
		private ClientThreadObserver() {
			this._checkCount = 14;
			this._logip = ClientThread.this._ip.toString();
		}

		@Override
		public void run() {
			try {
				while (true) {
					Thread.sleep(5000);
					if (ClientThread.this._csocket == null) {
						if (this._closeCount >= 1) {
							break;
						}
						this._closeCount++;
					}
					this._checkCount--;
					if (this._checkCount <= 0) {
						if (ClientThread.this._checkct > 0) {
							ClientThread.this._checkct = 0;
							this._checkCount = 14;
						} else {
							_log.info("因為不能在一定間內回應 (" + this._logip+ ")強制中斷連線。");
							break;
						}
					}
				}
				if (ClientThread.this._csocket != null){
					if(ClientThread.this._encrypt != null) {
						try {
							ClientThread.this._encrypt.encrypt(new S_Disconnect());
							ClientThread.this.close();
						} catch (final Exception e) {
							ClientThread.this.close();
						}
					} else {
						ClientThread.this.close();
					}
				} else {
					ClientThread.this.close();
				}

			} catch (final Exception e) {
				_log.log(Level.SEVERE, e.getLocalizedMessage(), e);
			}
		}
	}

	// キャラクターの行動処理スレッド
	class HcPacket implements Runnable {
		private final Queue<byte[]> _queue;

		private final PacketHandlerOfMove _handler;

		public HcPacket() {
			this._queue = new ConcurrentLinkedQueue<byte[]>();
			this._handler = new PacketHandlerOfMove(ClientThread.this);
		}

		public HcPacket(final int capacity) {
			this._queue = new LinkedBlockingQueue<byte[]>(capacity);
			this._handler = new PacketHandlerOfMove(ClientThread.this);
		}

		public void requestWork(final byte data[]) {
			this._queue.offer(data);
		}

		@Override
		public void run() {
			try {
				while (ClientThread.this._csocket != null) {
					byte[] data;
					data = this._queue.poll();
					if (data != null) {
						this._handler.handlePacket(data);
					} else {
						Thread.sleep(10);
					}
				}
			} catch (final Exception e) {
				_log.log(Level.SEVERE, e.getLocalizedMessage(), e);
			} finally {
				this._queue.clear();
			}
		}
	}

	// private InputStream _in;

	private static final Logger _log = Logger.getLogger(ClientThread.class.getName());

	// private OutputStream _out;
	private EncryptExecutor _encrypt;// 封包加密管理
	private DecryptExecutor _decrypt;// 封包解密管理

	private int _saveInventory = 0;

	private int _savePc = 0;

	private final PacketHandler _handler;// 資料處理者

	private L1Account _account = null;// 連線帳戶資料;

	private L1PcInstance _activeChar = null;// 登入人物資料

	private StringBuilder _ip = null;// 連線IP資料

	private StringBuilder _mac = null;// MAC資料

	public Socket _csocket;

	public boolean _isOnline = true;


	private int _error = -1;// 錯誤次數
	private static final int M_CAPACITY = 3; // 移動最大封包處理量

	private static final int H_CAPACITY = 2;// 人物其他動作最大封包處理量
	//	private static final int A = 3;// 攻擊最大封包處理量
	//	private static final int B = 3;// 遠距離攻擊最大封包處理量

	//	private static final int S = 3;// 技能最大封包處理量

	//	private static final int I = 2;// 使用物品最大封包處理量

	// TODO 伺服器綑綁
	public int _xorByte = (byte) 0xF0;

	public long _authdata;
	// TODO 伺服器綑綁
	private Encryption _keys;
	private StringBuilder stringBuilder;
	private boolean isError;// 完成要求接收伺服器版本(防止惡意封包發送)

	// ClientThreadによる一定間隔自動セーブを制限する為のフラグ（true:制限 false:制限無し）
	// 現在はC_LoginToServerが実行された際にfalseとなり、
	// C_NewCharSelectが実行された際にtrueとなる
	// @SuppressWarnings("unused")
	public boolean _charRestart = true;

	private boolean closing;

	private int _checkct = 1;

	public void CharReStart(boolean flag) {
		this._charRestart = flag;
	}

	/**
	 * 關閉連線線程
	 *
	 * @throws IOException
	 */
	public void close() {
		this._isOnline = false;
		try {
			if (this.closing) {
				return;
			}
			this.closing = true;
			if (this.isError) {
				// GameServer.getInstance().setBlock(_ip);
				_log.info("非法登入IP:" + this._ip);
				//				IpTable.getInstance().banIp(this._ip.toString() , this._ip.toString());
			}else{

				if (this._csocket != null) {
					try {
						if(!_csocket.isClosed()){
							this._encrypt.encrypt(new S_Disconnect());
						}
					} catch (final Exception e) {
					}
				}
			}


			if (this._decrypt != null) {
				try{
					this._decrypt.stop();
				}catch(final Exception e){
					_log.log(Level.SEVERE , e.getLocalizedMessage() , e);
				}
				this._decrypt = null;
			}
			if (this._encrypt != null) {
				try{
					this._encrypt.stop();
				}catch(final Exception e){
					_log.log(Level.SEVERE , e.getLocalizedMessage() , e);
				}
				this._encrypt = null;
			}
			StreamUtil.close(this._csocket);// 關閉IO
			this._csocket = null;

			// LoginController.getInstance().logout(this);
			// _kick = 0;
			String account = null;

			if (this._account != null) {
				account = this._account.get_login();
			}
			/*** 移出連線列表 */
			if (account != null) {
				OnlineUser.get().remove(account);
			}

			/*** 線上角色移除 */
			if (!this.isError) {
				this.quitGame(); // 登出角色

				if (this._ip != null) {

					if (this.stringBuilder == null) {
						this.stringBuilder = new StringBuilder();
					}
					this.stringBuilder.append("\n--------------------------------------------------\n(");
					// stringBuilder.append("\n       客戶端 離線: (");
					if (account != null) {
						this.stringBuilder.append(account + "  ");
					}
					if (this._mac != null) {
						this.stringBuilder.append(this._mac + " / ");
					}
					this.stringBuilder.append(this._ip.toString()+ ") 連線中斷 ");
					if(this._loc !=null){
						this.stringBuilder.append(_loc.getMapId()).append(" ").append(_loc.getX()).append(" ").append(_loc.getY());
					}
					this.stringBuilder.append("\n--------------------------------------------------");
					// stringBuilder.append("\n使用記憶體： " +
					// SystemUtil.getUsedMemoryMB() + "MB");
					// SystemUtil.printMemoryUsage(_log);
					_log.info(this.stringBuilder.toString());
					// System.out.println(stringBuilder.toString());
					this.stringBuilder.delete(0, this.stringBuilder.length());
				}
				GameServer.getInstance().removeOnlinePc(this);
			}
			this.closing = false;
			this._ip = null;
			// GeneralThreadPool.getInstance().cancel(this);
		} catch (final Exception e) {
			_log.log(Level.SEVERE, e.getLocalizedMessage(), e);
		}
	}

	/**
	 * 傳回錯誤次數
	 *
	 * @return
	 */
	public int get_error() {
		return this._error;
	}

	/**
	 * 傳回加密與解密金鑰
	 *
	 * @return the _keys
	 */
	public Encryption get_keys() {
		return this._keys;
	}

	/**
	 * 傳回自動存檔背包物件時間
	 *
	 * @return
	 */
	public int get_saveInventory() {
		return this._saveInventory;
	}

	/**
	 * 傳回自動存檔人物資料時間
	 *
	 * @return
	 */
	public int get_savePc() {
		return this._savePc;
	}

	/**
	 * 傳回 Socket
	 *
	 * @return
	 */
	public Socket get_socket() {
		return this._csocket;
	}

	/**
	 * 傳回帳號暫存資料
	 *
	 * @return
	 */
	public L1Account getAccount() {
		return this._account;
	}

	/**
	 * 傳回登入帳號
	 *
	 * @return
	 */
	public String getAccountName() {
		if (this._account == null) {
			return null;
		}
		return this._account.get_login();
	}

	/**
	 * 傳回目前登入人物
	 *
	 * @return
	 */
	public L1PcInstance getActiveChar() {
		return this._activeChar;
	}

	/**
	 * 傳回IP位置
	 *
	 * @return
	 */
	public StringBuilder getIp() {
		return this._ip;
	}

	/**
	 * 傳回MAC位置
	 *
	 * @return
	 */
	public StringBuilder getMac() {
		return this._mac;
	}

	/**
	 * 傳回封包加密解密管理接口
	 */
	public EncryptExecutor out() {
		return this._encrypt;
	}

	private void packetReceived() {
		this._checkct++;
	}

	/**
	 * 人物離開遊戲的處理
	 *
	 * @param pc
	 */
	public void quitGame() {
		if(this.getAccount() !=null){
			if(this.getAccount().onlineTimeSave()){//判定是否與預設時間不同，不同才更新。
				AccountReading.get().updateOnlineTime(this.getAccountName(), this.getAccount().getOnlineTime());
				this.getAccount().defaultOnlineTime();//重新同化預設時間
			}
		}
		if (this._activeChar == null) {
			final L1PcInstance pc = GameServer.getInstance().getOnlinePc(this);
			if (pc != null) {
				this._activeChar = pc;
				_log.warning("卡登玩家:" + pc.getAccountName() + " / "
						+ pc.getName() + " 已經被踢除。");
			} else {
				return;
			}
		}
		try {
			synchronized (this._activeChar) {
				QuitGame.quitGame(this._activeChar);
				this._activeChar = null;
			}
		} catch (final Exception e) {
			_log.log(Level.SEVERE, e.getLocalizedMessage(), e);
		}
	}

	/**
	 * 封包解密
	 *
	 * @return
	 * @throws Exception
	 */
	private byte[] readPacket() {
		try {
			byte[] data = null;
			data = this._decrypt.decrypt();
			return data;

		} catch (final Exception e) {
			// _log.log(Level.SEVERE,e.getLocalizedMessage(), e);
		}
		return null;
	}



	/**
	 * 設置錯誤次數
	 *
	 * @param error
	 */
	public void set_error(final int error) {
		this._error = error;
		if (error >= 2) {
			this.close();
		}
	}

	/**
	 * 加密與解密金鑰
	 */
	public void set_keys(Encryption keys) {
		// System.out.println("加密與解密金鑰:"+keys);
		this._keys = keys;
	}

	/**
	 * 設置自動存檔背包物件時間
	 *
	 * @param _saveInventory
	 */
	public void set_saveInventory(final int saveInventory) {
		this._saveInventory = saveInventory;
	}

	/**
	 * 設置自動存檔人物資料時間
	 *
	 * @param _saveInventory
	 */
	public void set_savePc(final int savePc) {
		this._savePc = savePc;
	}

	/**
	 * 設置登入帳號
	 *
	 * @param account
	 */
	public void setAccount(final L1Account account) {
		this._account = account;
	}

	/*
	 * @Override public void out(ServerBasePacket packet) { synchronized (this)
	 * { try { if(_csocket == null){ this.close(); return; } byte abyte0[] =
	 * packet.getContent(); if(abyte0.length <=0){return;} char ac[] = new
	 * char[abyte0.length]; ac = UChar8.fromArray(abyte0); ac =
	 * LineageEncryption.encrypt(ac, _clkey); abyte0 = UByte8.fromArray(ac);
	 * final int j = abyte0.length + 2;
	 *
	 * _out.write(j & 0xff); _out.write(j >> 8 & 0xff); _out.write(abyte0);
	 * _out.flush(); } catch (final Exception e) { //_log.log(Level.SEVERE,
	 * e.getLocalizedMessage(), e); } } }
	 */
	/**
	 * 設置目前登入人物
	 *
	 * @param pc
	 */
	public void setActiveChar(final L1PcInstance pc) {
		this._activeChar = pc;
	}

	/**
	 * 設置MAC位置
	 *
	 * @param mac
	 * @return true:允許登入 false:禁止登入
	 */
	public boolean setMac(final StringBuilder mac) {
		this._mac = mac;
		return true;
	}

	public void start() {

	}
	public L1Location _loc;

	private String _uuid ;

	public String get_UUID(){
		return _uuid;
	}

	private void getUUID(){
		BufferedWriter out = null;
		BufferedReader in = null;
		Socket serverSocket = null;
		try{
			serverSocket = new Socket();
			InetSocketAddress isa = new InetSocketAddress("127.0.0.1", 5092);
			serverSocket.connect(isa);
			out = new BufferedWriter(new OutputStreamWriter(serverSocket.getOutputStream()));
			out.write(this.getIp().toString());
			out.flush();
			in = new BufferedReader(new InputStreamReader(serverSocket.getInputStream())); 
			_uuid =in.readLine();
		}catch(Exception e){
		}finally{
			StreamUtil.close(serverSocket,in,out);
		}
	}

	private void setIp(){
		if(Config.SERVERCE !=9999){
			try{
				BufferedReader br = new BufferedReader(new InputStreamReader(_csocket.getInputStream())); 
				String line =br.readLine();
				if(line !=null){
					this._ip = new StringBuilder().append(line);
				}
			} catch (Exception e1) {
				e1.printStackTrace();
				this._ip = new StringBuilder().append(_csocket.getInetAddress().getHostAddress());
				_isOnline = false;
			}
		}else{
			this._ip = new StringBuilder().append(_csocket.getInetAddress().getHostAddress());
		}
	}
}
